-
I stopped trying on (2025-01-24).
-
-
Check if anything has changed.
-
Problems that made the experience terrible
-
Related to the time I tried in [2025-01].
SwiftGodot
-
All SwiftGodot documentation is wrong, incomplete, or insufficient.
-
Nonsensical things, where things stop working without any warning, or things that don’t work with no logic.
-
3000 different .dll dependencies that must be in the main .dll folder.
-
You need to copy all .dll files from a folder in AppData/Local/Programs/Swift/etc,etc,etc. It’s an inconvenient mess.
-
-
3000 different dependencies that must be mentioned in the GDExtension for ~exporting (I think, nobody knows for sure).
-
The devs have NO priority or even equipment to test things on Windows.
-
Could not make the LSP recognize the SwiftGodot import in VSCode.
-
The SwiftGodot repository is very disorganized.
Swift
-
TERRIBLE compilation time.
-
Compiling a "hello world" sometimes takes about 10 minutes.
-
There were some problems happening in Windows, but even after "solving" it, the comp time is still huge.
-
-
No binaries for Windows and Linux.
-
Tons of annoying dependencies to download on Windows, a very long installation process, where 3000 things can go wrong during it.
-
It can be ~simple, but at the same time it can cause problems and be exhausting.
-
-
Found Swift Package Manager to be terrible.
-
Every time I had to reset tons of caches, do package clean, delete Package.resolve to get a different result.
-
Several times the build did not run, with no indication of an error.
-
Sometimes I need to move the project folder.
-
-
The LSP is not good.
-
It did not work on Windows for Swift 6.0+, and months went by with no response on Github, so when they finally "fixed" the problem, the solution was for version 6.1.0 (pre-release), which is bizarre.
-
-
The project folder path cannot contain accents, which is annoying.
-
There is no "real" Windows support, as
.platformin Package.swift does not have this option, which is very strange. Swift can be used on Windows and exported as .dll or .exe, but this is not intuitive from that option. -
Apple; that should already be a no go.
-
Terrible ecosystem.
-
You need to enable Windows Developer Mode, which is a hassle.
-
Swift 6.0+ Concurrency system is strange and uncomfortable, breaking the convenience of using Swift for simpler tasks.
-
This was my initial impression, but maybe studying more about it will make it less inconvenient.
-
Anyway, having to study concurrency just to make a simple project compile is frustrating.
-
-
No tutorials.
Current problems
-
https://github.com/migueldeicaza/SwiftGodot/issues/160
-
https://github.com/migueldeicaza/SwiftGodot/issues/589
-
6.0+ :
-
https://github.com/migueldeicaza/SwiftGodot/issues/462
-
https://github.com/migueldeicaza/SwiftGodot/pull/612#issuecomment-2555523506
-
About
-
Swift Godot, to solve current C# problems .
-
About the speaker:
-
The person in the talk is funny.
-
A super Swift fan-boy.
-
Believes it’s the best language in the world.
-
Advocates for Godot to adopt it as the official language in the future.
-
Thinks writing code in C/C++ is archaic and should never be done.
-
Apple Fanboy.
-
-
{17:16} Swift features compared to C#.
-
{29:40} Using Swift in Godot.
-
GodotKit
-
-
Needs
libgodot, something like that, still experimental. -
"This API can be used to implement new capabilities in both Godot extensions, or to drive Godot entirely from Swift."
-
Tutorial .
-
Xogot (Swift Godot on iPad)
Setup and Compilation
Initialize Swift in the current folder as a Library
-
swift package init --type library
Build
-
swift build -
Using
swift build -debug-info-format codeviewwill generate the pdbs, which need to be placed alongside the dlls to stop Godot complaining.
GDExtension
[configuration]
entry_symbol = "swift_entry_point"
compatibility_minimum = 4.3
[libraries]
windows.debug.x86_64 = "res://bin/SimpleRunnerDriver.dll"
[dependencies]
windows.debug.x86_64 = {"res://bin/SwiftGodot.dll" : ""}
Dependencies
-
"This means copying all
*.dllfiles fromC:\Program Files\Swift\runtime-development\usr\bin\". -
C:\Users\caior\AppData\Local\Programs\Swift\Runtimes\6.1.0\usr\bin.-
Copied all files from this folder to the folder where
floresta.dllwas placed.
-
-
"Also add this section to your Godot
.gdextensionafter thelibrariessection. This allows the dlls to copy automatically when exporting your project."
[dependencies]
windows.debug = {
"res://bin/BlocksRuntime.dll" : "",
"res://bin/dispatch.dll" : "",
"res://bin/Foundation.dll" : "",
"res://bin/FoundationNetworking.dll" : "",
"res://bin/FoundationXML.dll" : "",
"res://bin/swiftCore.dll" : "",
"res://bin/swiftCRT.dll" : "",
"res://bin/swiftDispatch.dll" : "",
"res://bin/swiftDistributed.dll" : "",
"res://bin/swiftObservation.dll" : "",
"res://bin/swiftRegexBuilder.dll" : "",
"res://bin/swiftRemoteMirror.dll" : "",
"res://bin/swiftSwiftOnoneSupport.dll" : "",
"res://bin/swiftWinSDK.dll" : "",
"res://bin/swift_Concurrency.dll" : "",
"res://bin/swift_Differentiation.dll" : "",
"res://bin/swift_RegexParser.dll" : "",
"res://bin/swift_StringProcessing.dll" : "",
}
windows.release = {
"res://bin/BlocksRuntime.dll" : "",
"res://bin/dispatch.dll" : "",
"res://bin/Foundation.dll" : "",
"res://bin/FoundationNetworking.dll" : "",
"res://bin/FoundationXML.dll" : "",
"res://bin/swiftCore.dll" : "",
"res://bin/swiftCRT.dll" : "",
"res://bin/swiftDispatch.dll" : "",
"res://bin/swiftDistributed.dll" : "",
"res://bin/swiftObservation.dll" : "",
"res://bin/swiftRegexBuilder.dll" : "",
"res://bin/swiftRemoteMirror.dll" : "",
"res://bin/swiftSwiftOnoneSupport.dll" : "",
"res://bin/swiftWinSDK.dll" : "",
"res://bin/swift_Concurrency.dll" : "",
"res://bin/swift_Differentiation.dll" : "",
"res://bin/swift_RegexParser.dll" : "",
"res://bin/swift_StringProcessing.dll" : "",
}
-
SwiftGodot.dll-
"I'm not getting any SwiftGodot.dll being generated, I have no idea where to get this dll, but it worked even without it". "Your package is shared but by default SwiftGodot builds as static on Windows, which means that its contents will be linked in to your .dll - so that’s all you need."
-
To build SwiftGodot as a DLL you need to change the Package.swift for it, which requires a bit of a hack of the SwiftGodot package itself. I wouldn’t bother if you’re up & running - I don’t think it relates to the pdb problem (which I think you can ignore anyway for now), and it might break something else.
-
-
.a-
A ".a" is not going to help, it is not looked up.
-
Package.swift
// swift-tools-version: 5.10.1
import PackageDescription
let package = Package(
name: "floresta",
products: [
.library(
name: "floresta",
type: .dynamic,
targets: ["floresta"]
),
],
dependencies: [
.package(
name: "SwiftGodot",
path: ".build/checkouts/SwiftGodot"
)
],
targets: [
.target(
name: "floresta",
dependencies: [
"SwiftGodot"
],
swiftSettings: [.unsafeFlags(["-suppress-warnings"])]
),
.testTarget(
name: "floresta_test",
dependencies: ["floresta"]
)
]
)
Type of Library
-
A library needs to be
.dynamicon Windows, since Godot GDExtension primarily works with.dllfiles as dynamic libraries, not.libfiles.
Dependencies
-
(2025-01-23)
-
Used 5.10.1
-
Forked the SwiftGodot repository and referenced my fork within the .build folder.
dependencies: [ .package( name: "SwiftGodot", path: ".build/checkouts/SwiftGodot" ) ], -
Used a branch from my fork pointing to the commit before the Swift 6.0+ build system update.
-
-
Binary dependencies :
-
XCFramework is specific to Apple platform versions of SwiftGodot and likely won't work on Windows.
-
Targets
-
Maybe necessary to use
swiftSettings: [.unsafeFlags(["-suppress-warnings"])].
How I made it work
-
Swift 6.1.0.
Package.swift
-
.dynamic library
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "floresta",
products: [
.library(
name: "floresta",
type: .dynamic,
targets: ["floresta"]
),
],
dependencies: [
.package(
url: "https://github.com/migueldeicaza/SwiftGodot",
branch: "main"
)
],
targets: [
.target(
name: "floresta",
dependencies: [
"SwiftGodot"
],
swiftSettings: [.unsafeFlags(["-suppress-warnings"])]
),
.testTarget(
name: "floresta_test",
dependencies: ["floresta"]
)
]
)
GDExtension
[configuration]
entry_symbol = "swift_entry_point"
compatibility_minimum = 4.3
[libraries]
windows.debug.x86_64 = "res://swift/floresta.dll"
; windows.release.x86_64 = "res://swift/floresta.dll"
[dependencies]
windows.debug.x86_64 = {"res://bin/SwiftGodot.dll" : ""}
-
C:\Users\caior\AppData\Local\Programs\Swift\Runtimes\6.1.0\usr\bin.-
Copied all files from this folder to the folder where
floresta.dllwas placed.
-
Class registration
-
@Godot-
The
@Godotmacro does a few things, it creates a default constructor that follows the convention to call the parentinit()method and performs any registrations that you might have done in your class for variables or methods. -
When you use the
@Godotmacro, a number of additional macros can be used inside your class, like#signalto define signals ,@Callableto surface a method to Godot, and@Exportto surface properties . -
Behind the scenes these macros use the lower-level
ClassDBAPI to define functions, properties and their values.
-
-
Automatic way:
targets: [ .target( // this plugin will generate a source file visible to compiler with '#initSwiftExtension(cdecl: "swift_entry_point", types: [SpinningCube.self])' plugins: [ .plugin(name: "EntryPointGeneratorPlugin", package: "SwiftGodot") ] ) ] -
Simplified way:
import SwiftGodot #initSwiftExtension(cdecl: "swift_entry_point", types: [])import SwiftGodot #initSwiftExtension(cdecl: "swift_entry_point", types: [SpinningCube.self]) -
Extended way:
/// We register our new type when we are told that the scene is being loaded func setupScene (level: GDExtension.InitializationLevel) { if level == .scene { register(type: SpinningCube.self) } } // Export our entry point to Godot: @_cdecl("swift_entry_point") public func swift_entry_point( interfacePtr: OpaquePointer?, libraryPtr: OpaquePointer?, extensionPtr: OpaquePointer?) -> UInt8 { print ("SwiftGodot Extension loaded") guard let interfacePtr, let libraryPtr, let extensionPtr else { print ("Error: some parameters were not provided") return 0 } initializeSwiftModule(interfacePtr, libraryPtr, extensionPtr, initHook: setupScene, deInitHook: { x in }) return 1 }
Debugging in VSCode
-
“create a launch.json file” and select “LLDB”.
-
Things to change :
-
Add a line for
programwith the path to your Godot executable -
Add a line for
argswith any necessary arguments to launch your game, such as the path to your Godot project. -
specify the
cwd(current working directory)
-
-
Example:
{ "type": "lldb", "request": "launch", "name": "Launch Godot Game", "program": "path/to/your/Godot/executable", "args": ["--path", "path/to/your/game/project"], "cwd": "${workspaceFolder}" } -
If you want to attach to PID :
{ "version": "0.2.0", "configurations": [ { "type": "lldb", "request": "launch", "name": "Launch Godot Game", "program": "path/to/your/Godot/executable", "args": ["--path", "path/to/your/game/project"], "cwd": "${workspaceFolder}" }, { "type": "lldb", "request": "attach", "name": "Attach to PID", "pid": "${command:pickProcess}" } ] }-
"alternatively, if you do not need access to the source, you can use the
.binaryTargetfeature of SwiftPM and reference an.xcframeworkthat I have conveniently published on GitHub at SwiftGodotBinary ".
-
Explanations
GDScript Reference
Variables
-
Variables are not exposed to Godot
-
You can export properties though
-
@Export
async / await
-
Unlike GDScript, await can only be invoked from
asyncmethods.
func demo() async {
await someSignal.emitted
}
-
If you are inside of a function that is not async, you need to wrap your code in Task, like this:
func demo() {
// We are not an async function, but we can start a Task
Task {
await someSignal.emitted
}
}
Singletons
let pressed = Input.isActionPressed(ui_down)
Signals
Callable
-
In SwiftGodot, you can create
Callableinstances by directly passing a Swift function that takes an array ofVariantarguments, and returns an optionalVariantresult, like this:
func myCallback(args: borrowing Arguments)-> Variant? {
print ("MyCallback invoked with \(args.count) arguments")
return nil
}
let myCallable = Callable(myCallback)
-
Alternatively, you can use a StringName that binds a method that you have exported (via, the
@Callablemacro), like this:
@Callable func myCallback(message: String) {
GD.print(message)
}
-
You can call the callable in GDScript by invoking call() method of the exported Swift type.
MySwiftNode.myCallback.call("Hello from Swift!")
Resources
GDScript Reference: Types
Variant
-
You can create Variants from types that conform to the VariantStorable protocol.
-
This includes the following types:
-
Godot’s native types: GString, Vector, Rect, Transform, Plane, Quaternion, AABB, Basis, Projection, Int64, NodePaths, RIDs, Callable, GDictionary, Array and PackedArrays.
-
Swift types that SwiftGodot adds convenience conformances for: Bool, Int, String and Float
-
Godot’s objects: e.g. Node, Area2D
-
Your own subclasses of SwiftGodot.Object type.
-
Other types that you can manually conform to VariantStorable.
-
-
You wrap your data type by calling one of the
Variantconstructors, and then you can pass this variant to Godot functions that expect aVariant. -
For example, to pass the value
true:
let trueVaraint = Variant (true)
-
You can get a string representation of a variant by calling
Variant’sdescriptionmethod:
print (trueVariant.description)
-
If you have a
Variantand want to extract its value, you typically use this pattern:
/// This method will return nil if the Variant provided does not contain
/// a boolean value. Otherwise it will contain the boolean stored in the
/// variant.
func getBoolValue (variant: Variant) -> Bool? {
guard let boolValue = Bool (variant) else {
return nil
}
return boolValue
}
Arrays
@Export
var myResources: VariantCollection<Resource>
@Export
var myNodes: ObjectCollection<MySpinnerCube>
GlobalScope Reference
GlobalScope
-
Global functions and some constants had to be moved to classes to avoid polluting the global name space, and you can find them in the
GDclass.
Math Functions
-
The various GDScript Math functions like
abs,acos,atanas well as their helper functions likeclampare defined as static functions in theGDclass. -
Generally, you can use the Swift versions instead of the GDScript versions, which are more complete and do not incur a marshaling overhead.
Random
-
The random functions like
randi,randfare under theGDclass. -
Swift provides a set of random functions that are just as good, like Int.random(in:) or Double.random(in:) that operate on Swift ranges.
Nodes
onready
-
There is no support for
onready.
@Godot
class Demo: Node {
var myLabel: Node3D?
override func _ready () {
myLabel = getNode (path: "myLabel") as? Node3D
}
}
-
If you do not need to load the node right away, and you merely need to be able to access it, you can use this instead:
@Godot
class Main: Node {
@SceneTree(path: "CharacterBody2D") var player: PlayerController?
@SceneTree(path: "locations/spawnpoint") var spawnpoint: Node2D?
@SceneTree(path: "Telepoint") var teleportArea: Area2D?
}
class Demo: Node {
@BindNode var myLabel: Node3D
}
init
-
Not understood.
@Godot
class NetworkedNode: Node {
required init() {
super.init()
onInit()
}
required init(nativeHandle: UnsafeRawPointer) {
super.init(nativeHandle: nativeHandle)
onInit()
}
func onInit() {
print("Was init!")
}
}
export
-
Fields and properties:
@Export var number: Int @Export var AnotherNumber { get { ... } set { ... } -
Default values:
@Export var number: Int = 0 @Export var text: String? = nil // Allows for nil @Export var greeting = "Hello World" // Exported field specifies a default value -
Nodes:
@Export public var node: Node { get { return myInternalNode } set { print ("Setting the node") } }@Export public Node Node { get; set; }-
If you find yourself that you do not want to provide manual get/set properties in your export, and want to have an optional for one of the Object types, you can use something like this:
@Export(.nodeType, "Camera3D") var camera: Camera3D? = nil-
The parameter to
.nodeTypeneeds to match the type of the object.
-
-
Resources:
@Export var resource: Resource { get {} set {} }@Export var resource: AnimationNode@Export public var resource: Resource { get { return myInternalResource } set { print ("Setting my resource") } } -
Enums:
enum MyEnum: Int, CaseIterable { case first case second } @Godot class Sample: Node { @Export(.enum) var myValue: MyEnum } -
Customizing:
@Export(.range, "0,20,") var number: Int = 0@Export(.range, "-10,20,0.2") var number = 0@Export(.range, "0,100,1,or_greater,or_less") var number: Int = 0@Export(.file) var GameFile: String?@Export(.dir) var gameDirectory: String?@Export (.file, "*.txt") var GameFile: String?@Export (.globalFile, "*.png") var toolImage: String?@Export (.globalDir) var toolDir: String?@Export (.multilineText) var text: String?@Export(.expEasing) public transitionSpeed: Float = 0@Export(.colorNoAlpha) var color: Color { get {} set {} }
export_group
-
It is possible to group your exported properties inside the Godot Inspector with the
#exportGroupmacro. Every exported property after this annotation will be added to the group. Start a new group or use#export_group(””) to break out.#exportGroup("My Properties") @Export var number = 3 -
You can also specify that only properties with a given prefix be grouped, like this:
#exportGroup("My Properties", prefix: "health") @Export var health_reload_speed = 3 -
Groups cannot be nested, use
#exportSubgroupto create subgroups within a group.#exportSubgroup("Extra Properties") #export var string = "" #export var flag = false
tool
@Godot(.tool)
Useful Manipulations
Null Check
-
You can check in one go if a given variant contains a valid object and is of a given type, with the
asObject(_:)method, combined with Swift’s “let”:
func demo(input: Variant) {
if let node = input.asObject(Node.self) {
// We have a happy node inside 'input'
} else {
print ("The variant did not wrap an object, if it did, it was either nil, or was not of type Node")
}
}